#include "ATMOS_MasterServerDatabase.h"
#include "ATMOS_DatabaseConnectionInfo.h"
#include "ATMOS_GameClientInfo.h"
#include "ATMOS_GameServerInfo.h"

#include <stdlib.h>

bool parseBool( const std::string &str );
int parseInt( const std::string &str );

template <typename T>
std::string toString( T in )
{
	std::stringstream ss;
	ss << in;
	return ss.str();
}

using namespace ATMOS;

MasterServerDatabase::MasterServerDatabase() :
mConnection( 0 ),
mResult( 0 )
{
}

MasterServerDatabase::~MasterServerDatabase()
{
	if( mResult )
	{
		clearResult();
	}
}

void MasterServerDatabase::connect(  const DatabaseConnectionInfo &info )
{
	if( mConnection )
	{
		PQfinish( mConnection );
		mConnection = 0;
	}

	std::stringstream ss;
	ss	<< "user=" << info.username
		<< " password=" << info.password
		<< " dbname=" << info.name
		<< " port=" << info.port
		<< " host=" << info.ip;
	mConnection = PQconnectdb( ss.str().c_str() );

	if( !mConnection )
	{
		throw std::runtime_error( "failed to connect to database" );
	}

	doQuery( "DROP TABLE connected_game_clients" );
	doQuery( "DROP TABLE connected_game_servers" );

	doQuery( "CREATE TABLE connected_game_clients(guid char(19) PRIMARY KEY, name char(50), " \
		"port int, ip char(16), isConnectedToGameServer boolean, accountType int)" );

	doQuery( "CREATE TABLE connected_game_servers(guid char(19) PRIMARY KEY, name char(50), " \
		"port int, ip char(16), ping int, numPlayers int, maxPlayers int)" );
}

void MasterServerDatabase::disconnect()
{
	if( mConnection )
	{
		PQfinish( mConnection );
		mConnection = 0;
	}
}

void MasterServerDatabase::doQuery( const std::string &query, bool echoQuery )
{
	try
	{
		if( !mConnection )
		{
			throw std::runtime_error( "no connection to database" );
		}

		if( mResult )
		{
			clearResult();
		}

	// 	if( echoQuery )
	// 		std::cout << query << std::endl;
		mResult = PQexec( mConnection, query.c_str() );

		ExecStatusType status =  PQresultStatus( mResult );
		switch( status )
		{
		case PGRES_FATAL_ERROR:
			throw std::runtime_error( PQerrorMessage( mConnection ) );
			break;
		case PGRES_EMPTY_QUERY:
			throw std::runtime_error( "empty query" );
			break;
		case PGRES_COMMAND_OK:
		case PGRES_TUPLES_OK:
			break;
		}
	}
	catch( std::runtime_error &error )
	{
		ATMOS_THROW_RT("SQL query failed: " << error.what());
	}
}

PGresult *MasterServerDatabase::getResult()
{
	return mResult;
}

void MasterServerDatabase::clearResult()
{
	if( mResult )
	{
		PQclear( mResult );
		mResult = 0;
	}
}

void MasterServerDatabase::addConnectedGameServer( const GameServerInfo &info )
{
	std::stringstream ss;
	ss	<< "INSERT INTO connected_game_servers VALUES ("
		<< "'" << info.masterServerGUID << "', "
		<< "'" << info.name << "', "
		<< info.port << ", "
		<< "'" << info.ip << "', "
		<< "0, "
		<< 0 << ", "
		<< info.maxPlayers << ")";

	doQuery( ss.str().c_str() );
}

void MasterServerDatabase::addConnectedGameClient( const GameClientInfo &info )
{
	std::stringstream ss;
	ss	<< "INSERT INTO connected_game_clients VALUES ("
		<< "'" << info.masterServerGUID << "', "
		<< "'" << info.name << "', "
		<< info.port << ", "
		<< "'" << info.ip << "', "
		<< "false, "
		<< info.accountType << ") ";

	doQuery( ss.str().c_str() );
}

void MasterServerDatabase::removeConnectedGameClient( const std::string &guid )
{
	std::stringstream ss;
	ss	<< "DELETE FROM connected_game_clients WHERE guid='" << guid << "'";
	doQuery( ss.str().c_str() );
}

void MasterServerDatabase::removeConnectedGameServer( const std::string &guid )
{
	std::stringstream ss;
	ss	<< "DELETE FROM connected_game_servers WHERE guid='" << guid << "'";
	doQuery( ss.str().c_str() );	
}

void MasterServerDatabase::getGameServerInfo( const std::string &guid, GameServerInfo &info )
{
	std::stringstream ss;
	ss	<< "SELECT * FROM connected_game_servers WHERE guid='" << guid << "'";
	doQuery( ss.str().c_str() );

	info.masterServerGUID = guid;

	PGresult *result = getResult();
	info.name = PQgetvalue( result, 0, 1 );
	info.port = atoi( PQgetvalue( result, 0, 2 ) );
	info.ip = PQgetvalue( result, 0, 3 );
	info.ping = atoi( PQgetvalue( result, 0, 4 ) );
	info.numPlayers = atoi( PQgetvalue( result, 0, 5 ) );
	info.maxPlayers = atoi( PQgetvalue( result, 0, 6 ) );
}

void MasterServerDatabase::getGameClientInfo( const std::string &guid, GameClientInfo &info )
{
	std::stringstream ss;
	ss	<< "SELECT * FROM connected_game_clients WHERE guid='" << guid << "'";
	doQuery( ss.str().c_str() );

	PGresult *result = getResult();
	info.name = PQgetvalue( result, 0, 1 );
	info.port = atoi( PQgetvalue( result, 0, 2 ) );
	info.ip = PQgetvalue( result, 0, 3 );
	info.connectedToServer = parseBool( PQgetvalue( result, 0, 4 ) );
	info.accountType = parseInt( PQgetvalue( result, 0, 5 ) );
}

int MasterServerDatabase::getNumGameServers()
{
	std::stringstream ss;
	ss	<< "SELECT guid FROM connected_game_servers";
	doQuery( ss.str().c_str() );

	return PQntuples( getResult() );
}

int MasterServerDatabase::getNumOpenGameServers()
{
	std::stringstream ss;
	ss	<< "SELECT guid FROM connected_game_servers WHERE numPlayers>=maxPlayers";
	doQuery( ss.str().c_str() );
	return PQntuples( getResult() );
}

void MasterServerDatabase::getGameServers( std::list<std::string> &serversList, unsigned int offset,
	unsigned int count )
{
	std::stringstream ss;
	ss	<< "SELECT guid FROM connected_game_servers";
	doQuery( ss.str().c_str() );

	PGresult *result = getResult();
	unsigned int end = offset + count;
	unsigned int numTuples = PQntuples( result );

	if( offset > numTuples )
	{
		// completely out of range
		return;
	}

	// if our requested end is beyond the result
	if( offset + count > numTuples )
	{
		// cut down the request end
		end = numTuples;
	}

	for( unsigned int i = offset; i < end; ++i )
	{
		serversList.push_back( PQgetvalue( result, i, 0 ) );
	}
}

void MasterServerDatabase::getOpenGameServers( std::list<std::string> &serversList,
	unsigned int offset, unsigned int count )
{
	std::stringstream ss;
	ss	<< "SELECT guid FROM connected_game_servers WHERE numPlayers<maxPlayers";
	doQuery( ss.str().c_str() );

	PGresult *result = getResult();
	unsigned int end = offset + count;
	unsigned int numTuples = PQntuples( result );

	if( offset > numTuples )
	{
		// completely out of range
		return;
	}

	// if our requested end is beyond the result
 	if( offset + count > numTuples )
	{
		// cut down the request end
		end = numTuples;
	}

	for( unsigned int i = offset; i < end; ++i )
	{
		serversList.push_back( PQgetvalue( result, i, 0 ) );
	}
}

int MasterServerDatabase::getNumGameClients()
{
	std::stringstream ss;
	ss	<< "SELECT guid FROM connected_game_clients";
	doQuery( ss.str().c_str() );

	return PQntuples( getResult() );
}

void MasterServerDatabase::getGameClients( std::list<std::string> &clientsList, unsigned int offset,
	unsigned int count )
{
	std::stringstream ss;
	ss	<< "SELECT guid FROM connected_game_clients";
	doQuery( ss.str().c_str() );

	PGresult *result = getResult();
	unsigned int end = offset + count;
	unsigned int numTuples = PQntuples( result );

	if( offset > numTuples )
	{
		// completely out of range
		return;
	}

	// if our requested end is beyond the result
	if( offset + count > numTuples )
	{
		// cut down the request end
		end = numTuples;
	}

	for( unsigned int i = offset; i < end; ++i )
	{
		clientsList.push_back( PQgetvalue( result, i, 0 ) );
	}
}

bool MasterServerDatabase::isServerFull( const std::string &guid )
{
	std::stringstream ss;
	ss	<< "SELECT isServerFull FROM connected_game_servers WHERE guid='" << guid << "' AND numPlayers >= maxPlayers";
	doQuery( ss.str().c_str() );
	return (std::string(PQgetvalue( getResult(), 0, 0 )) == "true");
}

int MasterServerDatabase::getServerPlayerCount( const std::string &guid )
{
	std::stringstream ss;
	ss	<< "SELECT numPlayers FROM connected_game_servers WHERE guid='" << guid << "'";
	doQuery( ss.str().c_str() );
	return parseInt( PQgetvalue( getResult(), 0, 0 ) );
}

int MasterServerDatabase::getServerMaxPlayerCount( const std::string &guid )
{
	std::stringstream ss;
	ss	<< "SELECT maxPlayers FROM connected_game_servers WHERE guid='" << guid << "'";
	doQuery( ss.str().c_str() );
	return parseInt( PQgetvalue( getResult(), 0, 0 ) );
}

void MasterServerDatabase::incrementServerPlayerCount( const std::string &guid )
{
	int newCount = getServerPlayerCount( guid ) + 1;
	assert( newCount <= getServerMaxPlayerCount( guid ) );

	std::stringstream ss;
	ss	<< "UPDATE connected_game_servers SET numPlayers=" << newCount << " WHERE guid='" << guid << "'";
	doQuery( ss.str().c_str() );
}

void MasterServerDatabase::decrementServerPlayerCount( const std::string &guid )
{
	int newCount = getServerPlayerCount( guid ) - 1;
	assert( newCount >= 0 );

	std::stringstream ss;
	ss	<< "UPDATE connected_game_servers SET numPlayers=" << newCount << " WHERE guid='" << guid << "'";
	doQuery( ss.str().c_str() );
}

bool MasterServerDatabase::isClient( const std::string &guid )
{
	std::stringstream ss;
	ss	<< "SELECT guid FROM connected_game_clients WHERE guid='" << guid << "'";
	doQuery( ss.str().c_str() );
	return (PQntuples( getResult() ) > 0);
}

bool MasterServerDatabase::isServer( const std::string &guid )
{
	std::stringstream ss;
	ss	<< "SELECT guid FROM connected_game_servers WHERE guid='" << guid << "'";
	doQuery( ss.str().c_str() );
	return (PQntuples( getResult() ) > 0);
}

////////////////////////////////////////////////////////////////////////////////////////////////////
bool parseBool( const std::string &str )
{
	bool ret = false;
	if( str == "true" || str == "TRUE" || str == "1" )
		ret = true;
	else if( str == "false" || str == "FALSE" || str == "0" )
		ret = false;
	return ret;
}

int parseInt( const std::string &str )
{
	std::stringstream ss;
	ss << str;
	ss.seekg( 0 );

	int out = 0;
	ss >> out;
	return out;
}
